package cn.bugDance.infrastructure.persistent.repository;

import cn.bugDance.domain.strategy.model.entity.*;
import cn.bugDance.domain.strategy.model.valobj.RuleTreeLineVO;
import cn.bugDance.domain.strategy.model.valobj.StrategyConstant;
import cn.bugDance.domain.strategy.model.valobj.StrategyDelayJobQueueData;
import cn.bugDance.domain.strategy.repository.IStrategyRepository;
import cn.bugDance.infrastructure.persistent.dao.*;
import cn.bugDance.infrastructure.persistent.po.*;
import cn.bugDance.infrastructure.persistent.redis.IRedisService;
import cn.bugDance.types.common.Constants;
import cn.bugDance.types.enums.ResponseCode;
import cn.bugDance.types.exception.AppException;
import com.alibaba.fastjson.TypeReference;
import com.esotericsoftware.kryo.util.IntMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RList;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.sql.Time;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Repository
@Slf4j
public class StrategyRepository implements IStrategyRepository {

    @Resource
    private IStrategyDao strategyDao;
    @Resource
    private IRedisService redisService;
    @Resource
    private IStrategyAwardDao strategyAwardDao;
    @Resource
    private IRuleConfigDao ruleConfigDao;
    @Resource
    private IRuleTreeDao ruleTreeDao;
    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Resource
    private IRuleTreeLineDao ruleTreeLineDao;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;


    @Override
    public StrategyEntity queryStrategyByStrategyId(Long strategyId) {
        String cacheKey = Constants.Redis.STRATEGY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey);
        if(null != strategyEntity) return strategyEntity;

        Strategy strategy = strategyDao.getStrategyByStrategyId(strategyId);

        strategyEntity = new StrategyEntity();
        strategyEntity.setStrategyId(strategy.getStrategyId());
        strategyEntity.setRuleModels(strategy.getRuleModels());
        redisService.setValue(cacheKey, strategyEntity);
        return strategyEntity;
    }

    @Override
    public List<StrategyAwardEntity> queryStrategyAwardByStrategyId(Long strategyId) {
        String cacheKey = Constants.Redis.STRATEGY_AWARD + strategyId;
        List<StrategyAwardEntity> strategyAwardEntities = redisService.getValue(cacheKey);
        if(strategyAwardEntities != null) return strategyAwardEntities;

        strategyAwardEntities = new ArrayList<>();
        List<StrategyAward> strategyAwards = strategyAwardDao.getStrategyAwardByStrategyId(strategyId);
        for(StrategyAward strategyAward : strategyAwards){
            StrategyAwardEntity strategyAwardEntity = new StrategyAwardEntity();
            strategyAwardEntity.setStrategyId(strategyId);
            strategyAwardEntity.setAwardId(strategyAward.getAwardId());
            strategyAwardEntity.setAwardTitle(strategyAward.getAwardTitle());
            strategyAwardEntity.setAwardSubTitle(strategyAward.getAwardSubTitle());
            strategyAwardEntity.setRuleModels(strategyAward.getRuleModels());
            strategyAwardEntity.setAwardCount(strategyAward.getAwardCount());
            strategyAwardEntity.setSort(strategyAward.getSort());
            strategyAwardEntity.setRaffle(strategyAward.getRaffle());
            strategyAwardEntity.setAwardRate(strategyAward.getAwardRate());

            strategyAwardEntities.add(strategyAwardEntity);
        }
        redisService.setValue(cacheKey, strategyAwardEntities);
        return strategyAwardEntities;
    }

    @Override
    public RuleConfigEntity queryRuleConfigRule(Long strategyId, String ruleWeight) {
        String cacheKey = Constants.Redis.RULE_CONFIG_STRATEGY + strategyId + ":" + ruleWeight;
        RuleConfigEntity ruleConfigEntity = redisService.getValue(cacheKey);
        if(null != ruleConfigEntity) return ruleConfigEntity;
        RuleConfig ruleConfig = ruleConfigDao.getConfigStrategy(strategyId, ruleWeight);
        ruleConfigEntity = new RuleConfigEntity();
        ruleConfigEntity.setConfig(ruleConfig.getConfig());
        redisService.setValue(cacheKey, ruleConfigEntity);
        return ruleConfigEntity;
    }

    @Override
    public void storeToCache(List<Integer> rateTable, long rateRangeSum, String cacheRateTableKey, String cacheRateLevelKey) {
        redisService.setValue(cacheRateLevelKey, rateRangeSum);
        RList<Integer> rateTableList = redisService.getList(cacheRateTableKey);
        rateTableList.addAll(rateTable);
    }

    @Override
    public void storeAwardCount(String cacheAwardCountKey, Integer awardCount, Long strategyId, Integer awardId, Date endTime) {
        try{
            strategyAwardDao.updateStrategyAwardCountSurplus(strategyId, awardId);
        }catch (Exception e){
            log.info("【策略仓储层--storeAwardCount】: 初始化奖品库存余额失败, awardId: {}, strategyId: {}", awardId, strategyId);
            throw new AppException(ResponseCode.UN_ERROR.getCode(), ResponseCode.UN_ERROR.getInfo());
        }
        long expireTime = endTime.getTime() - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
        redisService.setValue(cacheAwardCountKey, awardCount, expireTime);
    }

    @Override
    public Long queryCacheRateRange(String cacheRateLevelKey) {
        return redisService.getValue(cacheRateLevelKey);
    }

    @Override
    public Integer randomRaffle(int randomIndex, String cacheRateTableKey) {
        List<Integer> rateTableList = redisService.getList(cacheRateTableKey);
         return rateTableList.get(randomIndex);
    }

    @Override
    public RuleTreeEntity queryRuleTreeByStrategyId(Long strategyId) {
        RuleTree ruleTree = ruleTreeDao.getRuleTreeByStrategyId(strategyId);
        RuleTreeEntity ruleTreeEntity = new RuleTreeEntity();
        ruleTreeEntity.setRootId(ruleTree.getRootId());
        ruleTreeEntity.setTreeName(ruleTree.getTreeName());
        ruleTreeEntity.setTreeDesc(ruleTree.getTreeDesc());
        ruleTreeEntity.setTreeId(ruleTree.getTreeId());
        ruleTreeEntity.setStrategyId(strategyId);
        return ruleTreeEntity;
    }

    @Override
    public RuleTreeEntity queryRuleTreeByStrategyIdFromCache(Long strategyId) {
        String cacheKey = Constants.Redis.RULE_TREE + strategyId;
        return redisService.getValue(cacheKey);
    }

    @Override
    public List<RuleTreeNodeEntity> queryRuleTreeNodeByStrategyId(Long strategyId) {
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.getRuleTreeNodeByStrategyId(strategyId);
        if(null == ruleTreeNodes) return null;
        List<RuleTreeNodeEntity> ruleTreeNodeEntities = new ArrayList<>();
        for(RuleTreeNode ruleTreeNode : ruleTreeNodes){
            RuleTreeNodeEntity ruleTreeNodeEntity = new RuleTreeNodeEntity();
            ruleTreeNodeEntity.setNodeId(ruleTreeNode.getNodeId());
            ruleTreeNodeEntity.setStrategyId(strategyId);
            ruleTreeNodeEntity.setRuleModel(ruleTreeNode.getRuleModel());

            ruleTreeNodeEntities.add(ruleTreeNodeEntity);
        }
        return ruleTreeNodeEntities;
    }

    @Override
    public void cacheRuleTree(RuleTreeEntity ruleTreeEntity) {
        String cacheKey = Constants.Redis.RULE_TREE + ruleTreeEntity.getStrategyId();
        redisService.setValue(cacheKey, ruleTreeEntity);
    }

    @Override
    public List<RuleTreeLineEntity> queryRuleTreeLineEntityByNodeFromAndStrategyId(Long nodeId, Long strategyId) {
        List<RuleTreeLine> ruleTreeLines = ruleTreeLineDao.getRuleTreeLineByNodeFromAndStrategyId(nodeId, strategyId);
        if(null == ruleTreeLines) return null;
        List<RuleTreeLineEntity> ruleTreeLineEntities = new ArrayList<>();
        for(RuleTreeLine ruleTreeLine : ruleTreeLines){
            RuleTreeLineEntity ruleTreeLineEntity = new RuleTreeLineEntity();
            ruleTreeLineEntity.setLineId(ruleTreeLine.getLineId());
            ruleTreeLineEntity.setNodeFrom(nodeId);
            ruleTreeLineEntity.setNodeTo(ruleTreeLine.getNodeTo());
            ruleTreeLineEntity.setStrategyId(strategyId);
            ruleTreeLineEntity.setRuleTreeLineVO(RuleTreeLineVO.valueOf(ruleTreeLine.getRuleTreeLineType()));

            ruleTreeLineEntities.add(ruleTreeLineEntity);
        }
        return ruleTreeLineEntities;
    }

    @Override
    public RuleConfigEntity queryRuleConfigRuleAward(Long strategyId, Integer awardId, String ruleLock) {
        String cacheKey = Constants.Redis.RULE_CONFIG_STRATEGY_AWARD + strategyId + ":" + awardId + ":" + ruleLock;
        RuleConfigEntity ruleConfigEntity = redisService.getValue(cacheKey);
        if(null != ruleConfigEntity) return ruleConfigEntity;

        RuleConfig ruleConfig = ruleConfigDao.getConfigStrategyAward(strategyId, awardId, ruleLock);
        ruleConfigEntity = new RuleConfigEntity();
        ruleConfigEntity.setStrategyId(strategyId);
        ruleConfigEntity.setAwardId(awardId);
        ruleConfigEntity.setRuleModel(ruleLock);
        ruleConfigEntity.setRuleDesc(ruleConfig.getRuleDesc());
        ruleConfigEntity.setConfig(ruleConfig.getConfig());
        ruleConfigEntity.setRuleType(ruleConfig.getRuleType());

        redisService.setValue(cacheKey, ruleConfigEntity);
        return ruleConfigEntity;
    }

    @Override
    public StrategyAwardEntity queryStrategyAwardByStrategyIdAndAwardId(Long strategyId, Integer awardId) {
        String cacheKey = Constants.Redis.STRATEGY_AWARD + strategyId + ":" + awardId;
        StrategyAwardEntity strategyAwardEntity = redisService.getValue(cacheKey);
        if(null != strategyAwardEntity) return strategyAwardEntity;

        StrategyAward strategyAward = strategyAwardDao.getStrategyAwardByStrategyIdAndAward(strategyId, awardId);
        strategyAwardEntity = new StrategyAwardEntity();
        strategyAwardEntity.setStrategyId(strategyId);
        strategyAwardEntity.setAwardId(awardId);
        strategyAwardEntity.setAwardTitle(strategyAward.getAwardTitle());
        strategyAwardEntity.setAwardSubTitle(strategyAward.getAwardSubTitle());
        strategyAwardEntity.setAwardCount(strategyAward.getAwardCount());
        strategyAwardEntity.setAwardCountSurplus(strategyAward.getAwardCountSurplus());
        strategyAwardEntity.setSort(strategyAward.getSort());
        strategyAwardEntity.setRaffle(strategyAward.getRaffle());
        strategyAwardEntity.setAwardRate(strategyAward.getAwardRate());
        strategyAwardEntity.setRuleModels(strategyAward.getRuleModels());

        redisService.setValue(cacheKey, strategyAwardEntity);
        return strategyAwardEntity;
    }

    @Override
    public boolean strategyAwardSubtractionQuota(Long strategyId, Integer awardId, Date endDateTime) {
        String cacheKey = Constants.Redis.STRATEGY_AWARD_COUNT + strategyId + ":" + awardId;

        long quotaSurplus = redisService.decr(cacheKey);
        if(quotaSurplus < 0){
            redisService.setValue(cacheKey, 0);
            return false;
        }

        quotaSurplus += 1;
        cacheKey = Constants.Redis.STRATEGY_AWARD_COUNT_LOCK + strategyId + ":" + awardId + ":" + quotaSurplus;
        long expireMills = endDateTime.getTime() - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
        return redisService.setNx(cacheKey, expireMills, TimeUnit.MILLISECONDS);
    }

    @Override
    public void sendAndProcessJobQueue(Long strategyId, Integer awardId) {
        String cacheKey = Constants.Redis.STRATEGY_AWARD + strategyId + ":" + awardId;
        StrategyAwardEntity strategyAwardEntity = redisService.getValue(cacheKey);
        StrategyDelayJobQueueData strategyDelayJobQueueData = new StrategyDelayJobQueueData();
        boolean stock = false;
        if(null != strategyAwardEntity){
            if(!StringUtils.isBlank(strategyAwardEntity.getRuleModels()) && strategyAwardEntity.ruleModels().contains(StrategyConstant.StrategyRule.RULE_STOCK)){
                strategyDelayJobQueueData.setStrategyId(strategyId);
                strategyDelayJobQueueData.setAwardId(awardId);
                stock = true;
            }
        }
        else{
            StrategyAward strategyAward = strategyAwardDao.getStrategyAwardByStrategyIdAndAward(strategyId, awardId);
            strategyAwardEntity = new StrategyAwardEntity();
            strategyAwardEntity.setStrategyId(strategyId);
            strategyAwardEntity.setAwardId(awardId);
            strategyAwardEntity.setAwardTitle(strategyAward.getAwardTitle());
            strategyAwardEntity.setAwardSubTitle(strategyAward.getAwardSubTitle());
            strategyAwardEntity.setAwardCount(strategyAward.getAwardCount());
            strategyAwardEntity.setAwardCountSurplus(strategyAward.getAwardCountSurplus());
            strategyAwardEntity.setSort(strategyAward.getSort());
            strategyAwardEntity.setRaffle(strategyAward.getRaffle());
            strategyAwardEntity.setAwardRate(strategyAward.getAwardRate());
            strategyAwardEntity.setRuleModels(strategyAward.getRuleModels());
            if(!StringUtils.isBlank(strategyAwardEntity.getRuleModels()) && strategyAwardEntity.ruleModels().contains(StrategyConstant.StrategyRule.RULE_STOCK)){
                strategyDelayJobQueueData.setStrategyId(strategyId);
                strategyDelayJobQueueData.setAwardId(awardId);
                stock = true;
            }
            redisService.setValue(cacheKey, strategyAwardEntity);
        }
        if(stock){
            String cacheJobQueueKey = Constants.Redis.STRATEGY_DELAY_JOB_QUEUE;
            RBlockingQueue<StrategyDelayJobQueueData> blockingQueue = redisService.getBlockingQueue(cacheJobQueueKey);
            RDelayedQueue<StrategyDelayJobQueueData> delayedQueue = redisService.getDelayedQueue(blockingQueue);

            delayedQueue.offer(strategyDelayJobQueueData, 3, TimeUnit.SECONDS);
        }
    }

    @Override
    public StrategyDelayJobQueueData takeStrategyJobQueue() {
        String cacheKey = Constants.Redis.STRATEGY_DELAY_JOB_QUEUE;
        RBlockingQueue<StrategyDelayJobQueueData> blockingQueue = redisService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();
    }

    @Override
    public void strategyAwardSubtractionQuotaDataBase(Long strategyId, Integer awardId) {
        strategyAwardDao.strategyAwardCountSurplusSubtraction(strategyId, awardId);
    }

    @Override
    public Integer queryAwardRuleLockValue(Long strategyId, Integer awardId) {


        return 0;
    }

    @Override
    public void storeToCacheOn(Map<AwardRateRangeEntity, Integer> rateTable, Integer sum, String cacheRateTableKey, String cacheRateLevelKey) {
        Map<AwardRateRangeEntity, Integer> rateMap = redisService.getMap(cacheRateTableKey);
        rateMap.putAll(rateTable);
        redisService.setValue(cacheRateLevelKey, sum);
    }

    @Override
    public Integer queryCacheRateRangeOn(String cacheRateLevelKey) {
        return redisService.getValue(cacheRateLevelKey);
    }

    @Override
    public Integer randomRaffleOn(int i, String cacheRateTableKey) {
        Map<Map<Integer, Integer>, Integer> rateTable = redisService.getMap(cacheRateTableKey);
        return 0;
    }

    @Override
    public Integer queryRateTableLen(String cacheRateTableKey) {
        Map<AwardRateRangeEntity, Integer> rateTable = redisService.getMap(cacheRateTableKey);
        return rateTable.size();
    }

    @Override
    public Map<AwardRateRangeEntity, Integer> queryCacheRateMap(String cacheRateTableKey) {
        return redisService.getMap(cacheRateTableKey);
    }

    @Override
    public AwardRateRangeEntity algorithmQueryThreadSearch(List<AwardRateRangeEntity> awardRateRangeEntities, int random) {

        List<CompletableFuture<AwardRateRangeEntity>> futures = awardRateRangeEntities.stream()
                .map(awardRateRangeEntity -> CompletableFuture.supplyAsync(() -> {
                    int from = awardRateRangeEntity.getFrom();
                    int to = awardRateRangeEntity.getTo();
                    if(from <= (random + 1) && (random + 1) <= to){
                        return awardRateRangeEntity;
                    }
                    return null;
                }, threadPoolExecutor))
                .collect(Collectors.toList());

        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        try{
            allFutures.join();
            for(CompletableFuture<AwardRateRangeEntity> future : futures){
                AwardRateRangeEntity result = future.getNow(null);
                if(result != null){
                    return result;
                }
            }
        }catch (CompletionException e){
            throw new AppException(ResponseCode.UN_ERROR.getCode(), ResponseCode.UN_ERROR.getInfo());
        }
        return null;
    }

}
